"""Kernal interface for IM tools."""

import os
import sys

from libskysh import __version__
from libskysh import loader
from libskysh import parser
from libskysh import exe
from libskysh.cmd import cmd_t, cmderr_t


def get_builtin_dir():
    """Returns the builtin dir."""
    if exe.main_is_frozen():
        builtins_dir = os.path.join(exe.get_main_dir(), "builtins")
    else:
        if __name__ == "__main__":
            script_dir = os.path.dirname(sys.argv[0])
        else:
            script_dir = os.path.dirname(__file__)
        builtins_dir = os.path.join(script_dir, "builtins")
    return builtins_dir

# -------------
# Uber-builtins
# -------------
# These are just commands that are "shipped" with skysh, these
# are built right into the kernal.
#
class help_cmd_t(cmd_t):
    """Produces a basic "help" system for the skysh."""
    
    name = "help"
    aliases = ["h"]

    desc = """\
usage: help COMMAND
Displays the help for a given COMMAND. Type HELP COMMANDS to get a
list of available commands."""
        
    def execute(self, argv):
        if len(argv) == 1:
            self.write(self.desc)
        elif len(argv) == 2 and argv[1].lower() == "commands":
            for c in self.kernal.get_all_cmds():
                out = "_ " + c.name
                if bool(c.short_desc):
                    out += "- " + c.short_desc
                self.write(out)
        elif len(argv) == 2:
            c = self.kernal.cmds.get(argv[1], None)
            if c:
                self.write("_ %s" % (c.desc))
                if c.aliases:
                    self.write("_ aliases: %s" % (`c.aliases`))
                    

class about_cmd_t(cmd_t):
    name = "about"
    short_desc = "display information about skysh"
    desc = short_desc

    def execute(self, argv):
        self.write("skysh version %s\n"
                   "Copyright Ian Blumel, 2009.\n"
                   "All Rights Reserved." % (__version__)
                   )


class loader_cmd_t(cmd_t):
    name = "load"
    aliases = ["ld"]
    short_desc = "read commands/macros from a file"
    desc = """\
usage: load PATH
loads commands and macros from the given PATH into the skysh.
"""
    def execute(self, argv):
        if len(argv) != 2:
            raise cmderr_t, "require path name"
        path = argv[1]
        if not os.path.isfile(path):
            raise cmderr_t, "supplied path is not a valid file"

        fl = loader.file_loader_t(path)
        if not bool(fl.commands) or bool(fl.macros):
            self.write("file does not contain commands or macros")
            return
        
        for c in fl.commands:
            nm = self.kernal.add_cmd(c, path)
            if nm: self.write("\tloaded command, %s" % (nm))
        for m in fl.macros:
            nm = self.kernal.add_macro(m, path)
            if nm: self.write("\tloaded macro, %s" % (nm))
        self.write("done loading")
    

class kernal_t(object):
    """The kernal handles all "Big Picture" items, but leaves the
    nitty details to each of the implementations. Specifically the
    shells."""
    def __init__(self, *args):
        self.cmds = {}
        self.cmds_src = {}
        self.cmd_aliases = {}
        self.macros = {}
        # Signals that we are processing a command.
        self.cmd_mode = False
        # Uber-builtin. For now... everyone gets these.
        self.add_cmd(help_cmd_t, __file__)
        self.add_cmd(about_cmd_t, __file__)
        self.add_cmd(loader_cmd_t, __file__)
   
    def start(self):
        raise NotImplementedError

    def stop(self):
        raise NotImplementedError

    def write(self, msg):
        raise NotImplementedError

    def write_err(self, msg):
        raise NotImplementedError

    def replace(self, msg):
        raise NotImplementedError

    def _is_cmd_exist(self, cmd):
        # Need to check name, and aliases.
        if cmd.name in self.cmds:
            return True
        for a in cmd.aliases:
            if a in self.cmd_aliases:
                return True
        return False

    def add_cmd(self, cmd_klass, src):
        cmd = cmd_klass(self)
        if not hasattr(cmd, "name"):
            # TODO - flag a warning...
            return None
        if not self._is_cmd_exist(cmd):
            self.cmds[cmd.name] = cmd
            self.cmds_src[cmd.name] = src
            for a in cmd.aliases:
                self.cmd_aliases[a] = cmd
        return cmd.name

    def get_all_cmds(self):
        cmds = self.cmds.values()
        cmds.sort(lambda c1, c2: cmp(c1.name, c2.name))
        return cmds

    def get_cmd(self, cmdname):
        """Returns a command, does not search through aliases. Raises a
        KeyError if it does not exist."""
        return self.cmds[cmdname]

    def load_builtins(self):
        """Walks the builtins directory, loading commands/macros."""
        builtins_dir = get_builtin_dir()
        self.load_from_dir(builtins_dir)
        
    def load_from_dir(self, dir_):
        cmds, macros = loader.load(dir_)
        map(lambda c, s=self: s.add_cmd(*c), cmds)
        # Ignore macros...
        
    def send(self, text):
        """When a message is being sent, you should call this.

        This function will do the right thing during a send event."""
        # Ignore anything sent while in an active command, this
        # prevents a recursive command invocation. Instead you
        # should use the kernal.invoke() ability.
        if self.cmd_mode:
            return
        self.sys_invoke(text)

    def sending(self, text):
        raise NotImplementedError("not yet written macro support")

    def sys_invoke(self, line):
        """Invokes a command just like os.system."""
        argv = parser.cmd_parser(line)        
        cmd = self.cmds.get(argv[0], None) \
              or self.cmd_aliases.get(argv[0], None)
        if cmd:
            self.cmd_mode = True
            try:
                cmd.execute(argv)
            except cmderr_t, e:
                self.write_err("error: " + str(e))
            self.cmd_mode = False

        
